﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace Dqaqs.ProfileTableAlgorithm.Helpers
{
	public interface IEpsilonReducerService
	{
		/// <summary>
		/// Implements the reduction part of the profile table algorithm
		/// </summary>
		DataTable ReduceData(DataTable orderedProfile, double epsilon);
	}

	internal class EpsilonReducerService : IEpsilonReducerService
	{
		private readonly string _groupingIdCol;

		public EpsilonReducerService(string groupingIdCol)
		{
			_groupingIdCol = groupingIdCol;
		}

		public DataTable ReduceData(DataTable orderedProfile, double epsilon)
		{
			//Cols are ordere by |ai|
			//Remove every group with less than t
			//Then roll up
			//Then filter everything where f(Q) is in range epsilon
			//Probably do these all using SQL. Easier.
			// with ted as select bla1, bla2, bla3 from blas group by bla1, bla2, bla3, bla4 where having count(*) < t
			// with roll as select *, count (*) as cnt, sum(f(x)) as val, sum(f(x))/count(*) as rate from ted rollup(bla1, bla2, bla3, bla4)
			// order by bla1, bla2, ...
			// Get above into R
			// User the stupid stack to filter ranges

			//Get the metric name
			var firstMetricCol = orderedProfile.Columns.Cast<DataColumn>().FirstOrDefault(c => c.ColumnName.Contains("Q("));
			if (firstMetricCol == null)
				throw new InvalidOperationException("There is no metric column!");
			var metricName = firstMetricCol.ColumnName;

			if (orderedProfile.Rows.Count == 0)
				throw new InvalidOperationException("There is no data in the profile!");
			var stk = new Stack<DataRow>();
			stk.Push(orderedProfile.Rows[0]);
			foreach (var row in orderedProfile.Rows.Cast<DataRow>().Skip(1).ToList())
			{
				//Check row with the top of stack.
				//If it is subset and in range, ignore row (row is deleted)
				//If it is subset not in range, keep it.
				//If it is not subset take the guy out of stack and put the new one it into the stack 
				//And ofcourse write it to output
				var isSubset = CheckLeftIsSubsetOfRight(row, stk.Peek());
				var isInRange = isSubset && CheckLeftIsInRange(row, stk.Peek(), metricName, epsilon);
				if (isSubset && isInRange)
				{
					orderedProfile.Rows.Remove(row);
				}
				if (isSubset && !isInRange)
				{
                    stk.Push(row);
				}
				if (!isSubset)
				{
				    while (stk.Count > 0 && !CheckLeftIsSubsetOfRight(row, stk.Peek()))
				    {
				        stk.Pop();
				    }
                    if (stk.Count == 0)
                        throw new DataException("A row with all '-' should exist in the profile.");
                    if (CheckLeftIsInRange(row, stk.Peek(), metricName, epsilon))
                        orderedProfile.Rows.Remove(row);
                    else
                        stk.Push(row);
				}
			}
			return orderedProfile;
		}

		private static bool CheckLeftIsInRange(DataRow left, DataRow right, string metricName, double e)
		{
			return Math.Abs((double) left[metricName] - (double) right[metricName]) <= e;
		}

		private bool CheckLeftIsSubsetOfRight(DataRow row, DataRow peek)
		{
		    var left = (int) row[_groupingIdCol];
			var right = (int)peek[_groupingIdCol];
			return (left | right).Equals(right) && left != right;
		}

	}
}
